
#include "fs.h"

// Return the virtual address of this disk block.
void *
diskaddr(uint32_t blockno) {
    if (blockno == 0 || (super && blockno >= super->s_nblocks))
        panic("bad block number %08x in diskaddr", blockno);
    return (char *) (DISKMAP + blockno * BLKSIZE);
}

// Is this virtual address mapped?
bool
va_is_mapped(void *va) {
    return (uvpd[PDX(va)] & PTE_P) && (uvpt[PGNUM(va)] & PTE_P);
}

// Is this virtual address dirty?
bool
va_is_dirty(void *va) {
    return (uvpt[PGNUM(va)] & PTE_D) != 0;
}

// Fault any disk block that is read in to memory by loading it from disk.
// 通过从磁盘加载任何读入内存的磁盘块而产生的故障。
static void
bc_pgfault(struct UTrapframe *utf) {
    void *addr = (void *) utf->utf_fault_va;
    uint32_t blockno = ((uint32_t) addr - DISKMAP) / BLKSIZE;
    int r;

    // Check that the fault was within the block cache region
    if (addr < (void *) DISKMAP || addr >= (void *) (DISKMAP + DISKSIZE))
        panic("page fault in FS: eip %08x, va %08x, err %04x",
              utf->utf_eip, addr, utf->utf_err);

    // Sanity check the block number.
    if (super && blockno >= super->s_nblocks)
        panic("reading non-existent block %08x\n", blockno);

    // Allocate a page in the disk map region, read the contents
    // of the block from the disk into that page.
    // 分配页来映射这个区域，接着从硬盘中读取块的内容到这个页中
    // Hint: first round addr to page boundary. fs/ide.c has code to read the disk.
    //
    // LAB 5: you code here:
    addr = ROUNDDOWN(addr, PGSIZE);
    int ret;
    if ((ret = sys_page_alloc(sys_getenvid(), addr, PTE_W | PTE_U | PTE_P)) < 0) {
        panic("bc_pgfault happened Error: %e", ret);
    }
    ide_read(blockno * BLKSECTS, addr, BLKSECTS); // 默认块大小为4KB，正好和系统分配的页大小一样
    // Clear the dirty bit for the disk block page since we just read the block from disk
    // 当我们读入某个块时，应该要清除这个块页对应上的脏页位(代表它已经不是脏页了)
    if ((r = sys_page_map(0, addr, 0, addr, uvpt[PGNUM(addr)] & PTE_SYSCALL)) < 0)
        panic("in bc_pgfault, sys_page_map: %e", r);

    // Check that the block we read was allocated. (exercise for
    // the reader: why do we do this *after* reading the block
    // in?)
    if (bitmap && block_is_free(blockno))
        panic("reading free block %08x\n", blockno);
}

// Flush the contents of the block containing VA out to disk if
// necessary, then clear the PTE_D bit using sys_page_map.
// If the block is not in the block cache or is not dirty, does
// nothing.
// Hint: Use va_is_mapped, va_is_dirty, and ide_write.
// Hint: Use the PTE_SYSCALL constant when calling sys_page_map.
// Hint: Don't forget to round addr down.
void
flush_block(void *addr) {
    uint32_t blockno = ((uint32_t) addr - DISKMAP) / BLKSIZE;
    if (addr < (void *) DISKMAP || addr >= (void *) (DISKMAP + DISKSIZE))
        panic("flush_block of bad va %08x", addr);

    // LAB 5: Your code here.
    addr = ROUNDDOWN(addr, PGSIZE);
    if (!va_is_mapped(addr) || !va_is_dirty(addr)) {
        return;
    }
    ide_write(blockno * BLKSECTS, (const void *) addr, BLKSECTS);//将对应的磁盘块写入到addr内存地址上
    int ret;
    if ((ret = sys_page_map(sys_getenvid(), addr, sys_getenvid(), addr, PTE_SYSCALL)) < 0) {
        panic("flush_block happened Error: %e", ret);
    }
}

// Test that the block cache works, by smashing the superblock and
// reading it back.
static void
check_bc(void) {
    struct Super backup;

    // back up super block
    memmove(&backup, diskaddr(1), sizeof backup);

    // smash it
    strcpy(diskaddr(1), "OOPS!\n");
    flush_block(diskaddr(1));
    assert(va_is_mapped(diskaddr(1)));
    assert(!va_is_dirty(diskaddr(1)));

    // clear it out
    sys_page_unmap(0, diskaddr(1));
    assert(!va_is_mapped(diskaddr(1)));

    // read it back in
    assert(strcmp(diskaddr(1), "OOPS!\n") == 0);

    // fix it
    memmove(diskaddr(1), &backup, sizeof backup);
    flush_block(diskaddr(1));

    // Now repeat the same experiment, but pass an unaligned address to
    // flush_block.

    // back up super block
    memmove(&backup, diskaddr(1), sizeof backup);

    // smash it
    strcpy(diskaddr(1), "OOPS!\n");

    // Pass an unaligned address to flush_block.
    flush_block(diskaddr(1) + 20);
    assert(va_is_mapped(diskaddr(1)));

    // Skip the !va_is_dirty() check because it makes the bug somewhat
    // obscure and hence harder to debug.
    //assert(!va_is_dirty(diskaddr(1)));

    // clear it out
    sys_page_unmap(0, diskaddr(1));
    assert(!va_is_mapped(diskaddr(1)));

    // read it back in
    assert(strcmp(diskaddr(1), "OOPS!\n") == 0);

    // fix it
    memmove(diskaddr(1), &backup, sizeof backup);
    flush_block(diskaddr(1));

    cprintf("block cache is good\n");
}

void
bc_init(void) {
    struct Super super;
    set_pgfault_handler(bc_pgfault);
    check_bc();

    // cache the super block by reading it once
    memmove(&super, diskaddr(1), sizeof super);
}

